.. _chapter_82_start:

Дополнительно о диапазонах
==========================

В предыдущей главе мы использовали в основном диапазоны int. На практике контейнеры,
алгоритмы и диапазоны почти всегда реализуются в виде шаблонов. Пример
пример print() в этой главе также был шаблоном:

.. code-block:: d

    void print(T)(T range)
    {
        // ...
    }


Недостатком реализации print() является то, что хотя она и требует, чтобы T
быть разновидностью InputRange, она не формализует это требование с помощью
ограничением шаблона. (Мы уже рассматривали ограничения шаблонов в главе :ref:`Больше шаблонов<глава_78_start>`)
Модуль std.range содержит шаблоны, которые полезны как в шаблонных
ограничениях, так и в статических операторах if.

Виды шаблонов диапазона
------------------------

Группа шаблонов с именами, начинающимися с is, определяет, удовлетворяет ли тип
удовлетворяет требованиям определенного вида диапазона. Например,
isInputRange!T отвечает на вопрос "является ли T InputRange?". Следующие
шаблоны для определения принадлежности типа к определенному общему виду диапазона:
* isInputRange
* isForwardRange
* isBidirectionalRange
* isRandomAccessRange
* isOutputRange
Соответственно, шаблонное ограничение print() может использовать isInputRange:

.. code-block:: d

    void print(T)(T range) if (isInputRange!T)
    {
        // ...
    }

В отличие от других, isOutputRange принимает два шаблонных параметра: Первый - это
тип диапазона, а второй - тип элемента. Возвращается true, если данный тип диапазона
позволяет выводить данный тип элемента. Например, следующее ограничение
требует, чтобы диапазон был OutputRange, который принимает элементы типа double
элементы:

.. code-block:: d

    void foo(T)(T range) if (isOutputRange!(T, double))
    {
        // ...
    }

При использовании в сочетании со статическими if эти ограничения могут определять
возможности пользовательских диапазонов. Например, если зависимый диапазон
определяемого пользователем диапазона является ForwardRange, определяемый пользователем диапазон может воспользоваться этим фактом и предоставить функцию save().
воспользоваться этим фактом и может также предоставить функцию save().
Рассмотрим это на примере диапазона, который производит отрицательные значения элементов
существующего диапазона (точнее, числовые дополнения элементов). Давайте
начнем только с функций InputRange:

.. code-block:: d

    struct Negative(T) if (isInputRange!T)
    {
        T range;
        bool empty()
        {
            return range.empty;
        }

        auto front()
        {
            return -range.front;
        }

        void popFront()
        {
            range.popFront();
        }
    }

Примечание:Как мы увидим ниже, тип возврата front может быть указан какElementType!T
также.
Единственная функциональность этого диапазона заключается в функции front, которая производит
отрицательный передний элемент исходного диапазона.
Как обычно, ниже приведена удобная функция, связанная с этим диапазоном:

.. code-block:: d

    Negative!T negative(T)(T range)
    {
        return Negative!T(range);
    }

Этот диапазон можно использовать, например, с FibonacciSeries, который был определен в предыдущей главе.
предыдущей главе:

.. code-block:: d

    struct FibonacciSeries
    {
        int current = 0;
        int next = 1;
        enum empty = false;
        int front() const
        {
            return current;
        }

        void popFront()
        {
            const nextNext = current + next;
            current = next;
            next = nextNext;
        }

        FibonacciSeries save() const
        {
            return this;
        }
    }
    // ...
    writeln(FibonacciSeries().take(5).negative);

Выходные данные содержат негативы первых пяти элементов серии:

.. code-block:: d

[0, -1, -1, -2, -3]


Естественно, будучи просто InputRange, Negative не может использоваться с алгоритмами
такими как cycle(), которые требуют ForwardRange:

.. code-block:: d

    writeln(FibonacciSeries()
            .take(5)
            .negative
            .cycle // ← ОШИБКА компиляции
            .take(10));


Однако если исходный диапазон уже является ForwardRange, то нет никаких причин
для Negative не предоставлять функцию save(). Это условие может быть
определить с помощью static if, и функция save() может быть предоставлена, если исходный диапазон
диапазон является ForwardRange. В этом случае достаточно просто вернуть новый объект Negative
построенный по копии исходного диапазона:

.. code-block:: d

    struct Negative(T) if (isInputRange!T)
    {
        // ...
        static if (isForwardRange!T)
        {
            Negative save()
            {
                return Negative(range.save);
            }
        }
    }

Добавление новой функции save() превращает Negative!FibonacciSeries в
ForwardRange, и вызов cycle() теперь может быть скомпилирован:

.. code-block:: d

    writeln(FibonacciSeries()
        .take(5)
        .negative
        .cycle // ← now compiles
        .take(10));

Вывод всего выражения можно описать так: возьмите первые пять элементов
ряда Фибоначчи, взять их отрицательные значения, циклировать их до бесконечности и взять первые
десять из этих элементов:
[0, -1, -1, -2, -3, 0, -1, -1, -2, -3]
Используя тот же подход, Negative можно сделать двунаправленным диапазоном и
RandomAccessRange, если исходный диапазон поддерживает эти функции:

.. code-block:: d

    struct Negative(T) if (isInputRange!T)
    {
        // ...
        static if (isBidirectionalRange!T)
        {
            auto back()
            {
                return -range.back;
            }

            void popBack()
            {
                range.popBack();
            }
        }
        static if (isRandomAccessRange!T)
        {
            auto opIndex(size_t index)
            {
                return -range[index];
            }
        }
    }

Например, когда он используется со фрагментом, отрицательные элементы могут быть доступны
с помощью оператора []:

.. code-block:: d

    auto d = [ 1.5, 2.75 ];
    auto n = d.negative;
    writeln(n[1]);

Вывод:

.. code-block:: console

    -2.75

.. index::
    ElementType
    ElementEncodingType
    

ElementType и ElementEncodingType
----------------------------------

ElementType указывает типы элементов диапазона.
Например, следующее ограничение шаблона включает требование
о типе элемента первого диапазона:

.. code-block:: d

    void foo(I1, I2, O)(I1 input1, I2 input2, O output)
        if (isInputRange!I1 &&
        isForwardRange!I2 &&
        isOutputRange!(O, ElementType!I1))
    {
        // ...
    }

Предыдущее ограничение можно описать так: если I1 - это InputRange,I2 - это
ForwardRange и O это anOutputRange которые принимают тип элемента I1.
Поскольку строки всегда являются диапазонами символов Unicode, независимо от их
они всегда являются диапазонами dchar, что означает, что даже
ElementType!string и ElementType!wstring являются dchar. По этой причине,
когда это необходимо в шаблоне, фактический тип кодировки UTF диапазона строк можно получить
получить с помощью ElementEncodingType.

Другие шаблоны диапазонов
-------------------------

Модуль std.range содержит множество других шаблонов диапазонов, которые можно использовать с другими возможностями D
другими функциями времени компиляции. Ниже приведена выборка:
* isInfinite: Является ли диапазон бесконечным
* hasLength: Имеет ли диапазон свойство длины
* hasSlicing: Поддерживает ли диапазон разбиение на фрагменты, т.е. с a[x..y]
* hasAssignableElements: Является ли возвращаемый тип front присваиваемым
* hasSwappableElements: Являются ли элементы диапазона взаимозаменяемыми
например, с помощью std.algorithm.swap
* hasMobileElements: Являются ли элементы диапазона подвижными, например, с помощью
std.algorithm.move
Это означает, что диапазон имеет функции moveFront(), moveBack() или moveAt(),
в зависимости от фактического вида диапазона. Поскольку перемещение элементов обычно
быстрее, чем их копирование, в зависимости от результата hasMobileElements диапазон
диапазон может обеспечить более быстрые операции, вызвав move().
* hasLvalueElements: Являются ли элементы диапазона l-значениями (приблизительно
означает, что элементы не являются копиями реальных элементов или
временные объекты, которые создаются на лету).
Например, hasLvalueElements!FibonacciSeries ложно, потому что
элементы FibonacciSeries не существуют сами по себе; скорее, они являются
копии члена current, который возвращается функцией front. Аналогично,
hasLvalueElements!(Negative!(int[])) ложно, потому что, хотя у среза
int имеет реальные элементы, диапазон, представленный Negative
не предоставляет доступа к этим элементам; скорее, он возвращает копии, имеющие
отрицательные знаки элементов реального среза. И наоборот,
hasLvalueElements!(int[]) истинно, потому что срез предоставляет доступ к
фактическим элементам массива.
В следующем примере используется преимущество isInfinite для предоставления empty в качестве
перечисления, когда исходный диапазон бесконечен, при этом во время компиляции становится известно, что
Negative!T также бесконечен:

.. code-block:: d

    struct Negative(T) if (isInputRange!T)
    {
        // ...
        static if (isInfinite!T)
        {
            // Negative!T is infinite as well
            enum empty = false;
        }
        else
        {
            bool empty()
            {
                return range.empty;
            }
        }
        // ...
    }

    static assert(isInfinite!(Negative!FibonacciSeries));
    static assert(!isInfinite!(int[]));

Полиморфизм во время выполнения с помощью inputRangeObject() и outputRangeObject()
----------------------------------------------------------------------------------

Будучи реализованными в основном в виде шаблонов, диапазоны демонстрируют полиморфизм во время компиляции
полиморфизм, который мы использовали в примерах этой и предыдущих глав.
этой и предыдущих глав. (О различиях между полиморфизмом времени компиляции
и полиморфизмом времени выполнения, см. раздел "Полиморфизм времени компиляции" в главе :ref:`Дополнительно о Шаблоны<глава_78_start>`
Полиморфизм во время компиляции имеет дело с тем фактом, что каждая инстанция
шаблона - это разные типы. Например, возвращаемый тип шаблона take()
напрямую связан с исходным диапазоном:

.. code-block:: d

    writeln(typeof([11, 22].negative.take(1)).stringof);
    writeln(typeof(FibonacciSeries().take(1)).stringof);

Вывод:

.. code-block:: console

    Take!(Negative!(int[]))
    Take!(FibonacciSeries)

Естественным следствием этого факта является то, что различные типы диапазонов не могут быть
назначать друг другу. Ниже приведен пример такой несовместимости
между двумя диапазонами InputRange:

.. code-block:: d

    auto range = [11, 22].negative;
    // ... at a later point ...
    range = FibonacciSeries(); // ← ошибка компиляции

Как и ожидалось, ошибка компиляции указывает на то, что FibonacciSeries и
Negative!(int[]) несовместимы:
Error: cannot implicitly convert expression (FibonacciSeries(0, 1))
типа FibonacciSeries в Negative!(int[]).
Однако, несмотря на то, что фактические типы диапазонов различны, поскольку они оба являются
диапазоны int, эту несовместимость можно рассматривать как ненужное ограничение. С
точки зрения использования, поскольку оба диапазона просто предоставляют элементы int, фактический
механизм, который производит эти элементы, не должен быть важен.
Phobos помогает решить эту проблему с помощью функций inputRangeObject() и
outputRangeObject(). inputRangeObject() позволяет представлять диапазоны как
конкретного вида диапазона определенных типов элементов. С ее помощью диапазон может быть использован
например, какInputRange элементовint, независимо от фактического типа диапазона.
inputRangeObject() достаточно гибок, чтобы поддерживать все невыводные
диапазоны: InputRange, ForwardRange, BidirectionalRange и
RandomAccessRange. Из-за этой гибкости возвращаемый ею объект не может быть
не может быть определен автоматически. Точный тип диапазона, который требуется, должен быть указан
явно:

.. code-block::d

    // Значение "InputRange of ints":
    InputRange!int range = [11, 22].negative.inputRangeObject;
    // ... в более поздний момент ...
    // Теперь компилируется следующее присваивание
    range = FibonacciSeries().inputRangeObject;

В качестве другого примера, когда диапазон необходимо использовать какForwardRange ofint
элементов, его тип должен быть указан явно как ForwardRange!int:


.. code-block:: d

    ForwardRange!int range = [11, 22].negative.inputRangeObject;
    auto copy = range.save;
    range = FibonacciSeries().inputRangeObject;
    writeln(range.save.take(10));


Пример вызывает save() только для того, чтобы доказать, что диапазоны действительно могут быть использованы как диапазоны
ForwardRange.
Аналогично, outputRangeObject() работает с диапазонами OutputRange и позволяет
использовать их в качестве диапазонаOutputRange, принимающего определенные типы элементов.

Резюме
-----------

* Модуль std.range содержит множество полезных шаблонов диапазонов.
* Некоторые из этих шаблонов позволяют шаблонам быть более функциональными в зависимости от
  возможностей исходных диапазонов.
* inputRangeObject() и outputRangeObject() обеспечивают полиморфизм во время выполнения.
  полиморфизм, позволяющий использовать различные типы диапазонов в качестве определенных типов
  диапазонов конкретных типов элементов.
